export const description = `
Execution tests for the 'textureLoad' builtin function

Reads a single texel from a texture without sampling or filtering.

Returns the unfiltered texel data.

An out of bounds access occurs if:
 * any element of coords is outside the range [0, textureDimensions(t, level)) for the corresponding element, or
 * array_index is outside the range [0, textureNumLayers(t)), or
 * level is outside the range [0, textureNumLevels(t))

If an out of bounds access occurs, the built-in function returns one of:
 * The data for some texel within bounds of the texture
 * A vector (0,0,0,0) or (0,0,0,1) of the appropriate type for non-depth textures
 * 0.0 for depth textures

`;

import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import {
  isCompressedFloatTextureFormat,
  isDepthTextureFormat,
  kAllTextureFormats,
  textureFormatAndDimensionPossiblyCompatible,
  isCompressedTextureFormat,
  kPossibleMultisampledTextureFormats,
  kDepthTextureFormats,
  kPossibleStorageTextureFormats,
} from '../../../../../format_info.js';
import { AllFeaturesMaxLimitsGPUTest, GPUTest } from '../../../../../gpu_test.js';
import { maxMipLevelCount, virtualMipSize } from '../../../../../util/texture/base.js';

import {
  TextureCall,
  checkCallResults,
  chooseTextureSize,
  createTextureWithRandomDataAndGetTexels,
  doTextureCalls,
  appendComponentTypeForFormatToTextureType,
  vec1,
  vec2,
  vec3,
  kSamplePointMethods,
  kShortShaderStages,
  generateTextureBuiltinInputs1D,
  generateTextureBuiltinInputs2D,
  generateTextureBuiltinInputs3D,
  Dimensionality,
  createCanvasWithRandomDataAndGetTexels,
  ShortShaderStage,
  isFillable,
} from './texture_utils.js';

export function normalizedCoordToTexelLoadTestCoord<T extends Dimensionality>(
  descriptor: GPUTextureDescriptor,
  mipLevel: number,
  coordType: 'i32' | 'u32',
  v: T
): T {
  const size = virtualMipSize(descriptor.dimension ?? '2d', descriptor.size, mipLevel);
  return v.map((v, i) => {
    const t = v * size[i];
    return coordType === 'u32' ? Math.abs(Math.round(t)) : Math.round(t);
  }) as T;
}

function skipIfStorageTexturesNotSupportedInStage(t: GPUTest, stage: ShortShaderStage) {
  if (t.isCompatibility) {
    t.skipIf(
      stage === 'f' && !(t.device.limits.maxStorageTexturesInFragmentStage! > 0),
      'device does not support storage textures in fragment shaders'
    );
    t.skipIf(
      stage === 'v' && !(t.device.limits.maxStorageTexturesInVertexStage! > 0),
      'device does not support storage textures in vertex shaders'
    );
  }
}

export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);

g.test('sampled_1d')
  .specURL('https://www.w3.org/TR/WGSL/#textureload')
  .desc(
    `
C is i32 or u32

fn textureLoad(t: texture_1d<T>, coords: C, level: C) -> vec4<T>

Parameters:
 * t: The sampled texture to read from
 * coords: The 0-based texel coordinate
 * level: The mip level, with level 0 containing a full size version of the texture
`
  )
  .params(u =>
    u
      .combine('stage', kShortShaderStages)
      .combine('format', kAllTextureFormats)
      .filter(t => textureFormatAndDimensionPossiblyCompatible('1d', t.format))
      // 1d textures can't have a height !== 1
      .filter(t => !isCompressedTextureFormat(t.format))
      .beginSubcases()
      .combine('samplePoints', kSamplePointMethods)
      .combine('C', ['i32', 'u32'] as const)
      .combine('L', ['i32', 'u32'] as const)
  )
  .fn(async t => {
    const { format, stage, C, L, samplePoints } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureFormatAndDimensionNotCompatible(format, '1d');

    // We want at least 4 blocks or something wide enough for 3 mip levels.
    const [width] = chooseTextureSize({ minSize: 8, minBlocks: 4, format });
    const size = [width, 1];

    const descriptor: GPUTextureDescriptor = {
      format,
      dimension: '1d',
      size,
      usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
    };
    const { texels, texture } = await createTextureWithRandomDataAndGetTexels(t, descriptor);

    const calls: TextureCall<vec1>[] = generateTextureBuiltinInputs1D(50, {
      method: samplePoints,
      descriptor,
      mipLevel: { num: texture.mipLevelCount, type: L },
      hashInputs: [stage, format, samplePoints, C, L],
    }).map(({ coords, mipLevel }, i) => {
      return {
        builtin: 'textureLoad',
        coordType: C === 'i32' ? 'i' : 'u',
        levelType: L === 'i32' ? 'i' : 'u',
        mipLevel,
        coords: normalizedCoordToTexelLoadTestCoord(descriptor, mipLevel, C, coords),
      };
    });

    const textureType = appendComponentTypeForFormatToTextureType('texture_1d', texture.format);
    const viewDescriptor = {};
    const sampler = undefined;
    const results = await doTextureCalls(
      t,
      texture,
      viewDescriptor,
      textureType,
      sampler,
      calls,
      stage
    );
    const res = await checkCallResults(
      t,
      { texels, descriptor, viewDescriptor },
      textureType,
      sampler,
      calls,
      results,
      stage,
      texture
    );
    t.expectOK(res);
  });

g.test('sampled_2d')
  .specURL('https://www.w3.org/TR/WGSL/#textureload')
  .desc(
    `
C is i32 or u32
L is i32 or u32

fn textureLoad(t: texture_2d<T>, coords: vec2<C>, level: L) -> vec4<T>

Parameters:
 * t: The sampled texture to read from
 * coords: The 0-based texel coordinate
 * level: The mip level, with level 0 containing a full size version of the texture
`
  )
  .params(u =>
    u
      .combine('stage', kShortShaderStages)
      .combine('format', kAllTextureFormats)
      .filter(t => !isCompressedFloatTextureFormat(t.format))
      .beginSubcases()
      .combine('samplePoints', kSamplePointMethods)
      .combine('C', ['i32', 'u32'] as const)
      .combine('L', ['i32', 'u32'] as const)
  )
  .fn(async t => {
    const { format, stage, samplePoints, C, L } = t.params;
    t.skipIfTextureFormatNotSupported(format);

    // We want at least 4 blocks or something wide enough for 3 mip levels.
    const size = chooseTextureSize({ minSize: 8, minBlocks: 4, format });

    const descriptor: GPUTextureDescriptor = {
      format,
      size,
      usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
      mipLevelCount: maxMipLevelCount({ size }),
    };
    const { texels, texture } = await createTextureWithRandomDataAndGetTexels(t, descriptor);

    const calls: TextureCall<vec2>[] = generateTextureBuiltinInputs2D(50, {
      method: samplePoints,
      descriptor,
      mipLevel: { num: texture.mipLevelCount, type: L },
      hashInputs: [stage, format, samplePoints, C, L],
    }).map(({ coords, mipLevel }) => {
      return {
        builtin: 'textureLoad',
        coordType: C === 'i32' ? 'i' : 'u',
        levelType: L === 'i32' ? 'i' : 'u',
        mipLevel,
        coords: normalizedCoordToTexelLoadTestCoord(descriptor, mipLevel, C, coords),
      };
    });

    const textureType = appendComponentTypeForFormatToTextureType('texture_2d', texture.format);
    const viewDescriptor = {};
    const sampler = undefined;
    const results = await doTextureCalls(
      t,
      texture,
      viewDescriptor,
      textureType,
      sampler,
      calls,
      stage
    );
    const res = await checkCallResults(
      t,
      { texels, descriptor, viewDescriptor },
      textureType,
      sampler,
      calls,
      results,
      stage,
      texture
    );
    t.expectOK(res);
  });

g.test('sampled_3d')
  .specURL('https://www.w3.org/TR/WGSL/#textureload')
  .desc(
    `
C is i32 or u32

fn textureLoad(t: texture_3d<T>, coords: vec3<C>, level: C) -> vec4<T>

Parameters:
 * t: The sampled texture to read from
 * coords: The 0-based texel coordinate
 * level: The mip level, with level 0 containing a full size version of the texture
`
  )
  .params(u =>
    u
      .combine('stage', kShortShaderStages)
      .combine('format', kAllTextureFormats)
      .filter(t => textureFormatAndDimensionPossiblyCompatible('3d', t.format))
      .filter(t => !isCompressedFloatTextureFormat(t.format))
      .beginSubcases()
      .combine('samplePoints', kSamplePointMethods)
      .combine('C', ['i32', 'u32'] as const)
      .combine('L', ['i32', 'u32'] as const)
  )
  .fn(async t => {
    const { format, stage, samplePoints, C, L } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureFormatAndDimensionNotCompatible(format, '3d');

    // We want at least 4 blocks or something wide enough for 3 mip levels.
    const size = chooseTextureSize({ minSize: 8, minBlocks: 4, format, viewDimension: '3d' });

    const descriptor: GPUTextureDescriptor = {
      format,
      dimension: '3d',
      size,
      usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
      mipLevelCount: maxMipLevelCount({ size }),
    };
    const { texels, texture } = await createTextureWithRandomDataAndGetTexels(t, descriptor);

    const calls: TextureCall<vec3>[] = generateTextureBuiltinInputs3D(50, {
      method: samplePoints,
      descriptor,
      mipLevel: { num: texture.mipLevelCount, type: L },
      hashInputs: [stage, format, samplePoints, C, L],
    }).map(({ coords, mipLevel }) => {
      return {
        builtin: 'textureLoad',
        coordType: C === 'i32' ? 'i' : 'u',
        levelType: L === 'i32' ? 'i' : 'u',
        mipLevel,
        coords: normalizedCoordToTexelLoadTestCoord(descriptor, mipLevel, C, coords),
      };
    });

    const textureType = appendComponentTypeForFormatToTextureType('texture_3d', texture.format);
    const viewDescriptor = {};
    const sampler = undefined;
    const results = await doTextureCalls(
      t,
      texture,
      viewDescriptor,
      textureType,
      sampler,
      calls,
      stage
    );
    const res = await checkCallResults(
      t,
      { texels, descriptor, viewDescriptor },
      textureType,
      sampler,
      calls,
      results,
      stage,
      texture
    );
    t.expectOK(res);
  });

g.test('multisampled')
  .specURL('https://www.w3.org/TR/WGSL/#textureload')
  .desc(
    `
C is i32 or u32
S is i32 or u32

fn textureLoad(t: texture_multisampled_2d<T>, coords: vec2<C>, sample_index: S)-> vec4<T>
fn textureLoad(t: texture_depth_multisampled_2d, coords: vec2<C>, sample_index: S)-> f32

Parameters:
 * t: The sampled texture to read from
 * coords: The 0-based texel coordinate
 * sample_index: The 0-based sample index of the multisampled texture
`
  )
  .params(u =>
    u
      .combine('stage', kShortShaderStages)
      .combine('texture_type', [
        'texture_multisampled_2d',
        'texture_depth_multisampled_2d',
      ] as const)
      .combine('format', kPossibleMultisampledTextureFormats)
      // Filter out texture_depth_multisampled_2d with non-depth formats
      .filter(
        t =>
          !(t.texture_type === 'texture_depth_multisampled_2d' && !isDepthTextureFormat(t.format))
      )
      .beginSubcases()
      .combine('samplePoints', kSamplePointMethods)
      .combine('C', ['i32', 'u32'] as const)
      .combine('S', ['i32', 'u32'] as const)
  )
  .fn(async t => {
    const { texture_type, format, stage, samplePoints, C, S } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureLoadNotSupportedForTextureType(texture_type);
    t.skipIfTextureFormatNotMultisampled(format);

    const sampleCount = 4;
    const descriptor: GPUTextureDescriptor = {
      format,
      size: [8, 8],
      usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
      sampleCount,
    };
    const { texels, texture } = await createTextureWithRandomDataAndGetTexels(t, descriptor);

    const calls: TextureCall<vec2>[] = generateTextureBuiltinInputs2D(50, {
      method: samplePoints,
      descriptor,
      sampleIndex: { num: texture.sampleCount, type: S },
      hashInputs: [stage, format, samplePoints, C, S],
    }).map(({ coords, sampleIndex }) => {
      return {
        builtin: 'textureLoad',
        coordType: C === 'i32' ? 'i' : 'u',
        sampleIndexType: S === 'i32' ? 'i' : 'u',
        sampleIndex,
        coords: normalizedCoordToTexelLoadTestCoord(descriptor, 0, C, coords),
      };
    });

    const textureType = appendComponentTypeForFormatToTextureType(texture_type, texture.format);
    const viewDescriptor = {};
    const sampler = undefined;
    const results = await doTextureCalls(
      t,
      texture,
      viewDescriptor,
      textureType,
      sampler,
      calls,
      stage
    );
    const res = await checkCallResults(
      t,
      { texels, descriptor, viewDescriptor },
      textureType,
      sampler,
      calls,
      results,
      stage,
      texture
    );
    t.expectOK(res);
  });

g.test('depth')
  .specURL('https://www.w3.org/TR/WGSL/#textureload')
  .desc(
    `
C is i32 or u32

fn textureLoad(t: texture_depth_2d, coords: vec2<C>, level: L) -> f32

Parameters:
 * t: The sampled texture to read from
 * coords: The 0-based texel coordinate
 * level: The mip level, with level 0 containing a full size version of the texture
`
  )
  .params(u =>
    u
      .combine('stage', kShortShaderStages)
      .combine('format', kDepthTextureFormats)
      .beginSubcases()
      .combine('samplePoints', kSamplePointMethods)
      .combine('C', ['i32', 'u32'] as const)
      .combine('L', ['i32', 'u32'] as const)
  )
  .fn(async t => {
    const { format, stage, samplePoints, C, L } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureLoadNotSupportedForTextureType('texture_depth_2d');

    // We want at least 4 blocks or something wide enough for 3 mip levels.
    const size = chooseTextureSize({ minSize: 8, minBlocks: 4, format });

    const descriptor: GPUTextureDescriptor = {
      format,
      size,
      usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
      mipLevelCount: maxMipLevelCount({ size }),
    };
    const { texels, texture } = await createTextureWithRandomDataAndGetTexels(t, descriptor);

    const calls: TextureCall<vec2>[] = generateTextureBuiltinInputs2D(50, {
      method: samplePoints,
      descriptor,
      mipLevel: { num: texture.mipLevelCount, type: L },
      hashInputs: [stage, format, samplePoints, C, L],
    }).map(({ coords, mipLevel }) => {
      return {
        builtin: 'textureLoad',
        coordType: C === 'i32' ? 'i' : 'u',
        levelType: L === 'i32' ? 'i' : 'u',
        mipLevel,
        coords: normalizedCoordToTexelLoadTestCoord(descriptor, mipLevel, C, coords),
      };
    });
    const textureType = 'texture_depth_2d';
    const viewDescriptor = {};
    const sampler = undefined;
    const results = await doTextureCalls(
      t,
      texture,
      viewDescriptor,
      textureType,
      sampler,
      calls,
      stage
    );
    const res = await checkCallResults(
      t,
      { texels, descriptor, viewDescriptor },
      textureType,
      sampler,
      calls,
      results,
      stage,
      texture
    );
    t.expectOK(res);
  });

g.test('external')
  .specURL('https://www.w3.org/TR/WGSL/#textureload')
  .desc(
    `
C is i32 or u32

fn textureLoad(t: texture_external, coords: vec2<C>) -> vec4<f32>

Parameters:
 * t: The sampled texture to read from
 * coords: The 0-based texel coordinate.
`
  )
  .params(u =>
    u
      .combine('stage', kShortShaderStages)
      .beginSubcases()
      .combine('importExternalTexture', [false, true])
      .combine('samplePoints', kSamplePointMethods)
      .combine('C', ['i32', 'u32'] as const)
      .combine('L', ['i32', 'u32'] as const)
  )
  .fn(async t => {
    const { stage, importExternalTexture, samplePoints, C, L } = t.params;

    const size = [8, 8, 1];

    // Note: external texture doesn't use this descriptor.
    // It's used to pass to the softwareTextureRead functions.
    const descriptor: GPUTextureDescriptor = {
      format: 'rgba8unorm',
      size,
      usage: GPUTextureUsage.COPY_DST,
    };

    t.skipIf(typeof OffscreenCanvas === 'undefined', 'OffscreenCanvas is not supported');
    const { texels, canvas } = createCanvasWithRandomDataAndGetTexels(descriptor.size);

    let videoFrame: VideoFrame | undefined;
    let texture: GPUExternalTexture | GPUTexture;
    if (importExternalTexture) {
      t.skipIf(typeof VideoFrame === 'undefined', 'VideoFrames are not supported');

      videoFrame = new VideoFrame(canvas, { timestamp: 0 });
      texture = t.device.importExternalTexture({ source: videoFrame });
    } else {
      texture = t.createTextureTracked({
        format: descriptor.format,
        size: descriptor.size,
        usage:
          GPUTextureUsage.COPY_DST |
          GPUTextureUsage.RENDER_ATTACHMENT |
          GPUTextureUsage.TEXTURE_BINDING,
      });
      t.queue.copyExternalImageToTexture(
        { source: canvas },
        { texture, premultipliedAlpha: true },
        size
      );
    }

    const calls: TextureCall<vec2>[] = generateTextureBuiltinInputs2D(50, {
      method: samplePoints,
      descriptor,
      hashInputs: [samplePoints, C, L],
    }).map(({ coords }) => {
      return {
        builtin: 'textureLoad',
        coordType: C === 'i32' ? 'i' : 'u',
        coords: normalizedCoordToTexelLoadTestCoord(descriptor, 0, C, coords),
      };
    });

    const textureType = 'texture_external';
    const viewDescriptor = {};
    const sampler = undefined;
    const results = await doTextureCalls(
      t,
      texture,
      viewDescriptor,
      textureType,
      sampler,
      calls,
      stage
    );
    const res = await checkCallResults(
      t,
      { texels, descriptor, viewDescriptor },
      textureType,
      sampler,
      calls,
      results,
      stage
    );
    t.expectOK(res);
    videoFrame?.close();
  });

g.test('arrayed')
  .specURL('https://www.w3.org/TR/WGSL/#textureload')
  .desc(
    `
C is i32 or u32

fn textureLoad(t: texture_2d_array<T>, coords: vec2<C>, array_index: A, level: L) -> vec4<T>
fn textureLoad(t: texture_depth_2d_array, coords: vec2<C>, array_index: A, level: L) -> f32

Parameters:
 * t: The sampled texture to read from
 * coords: The 0-based texel coordinate
 * array_index: The 0-based texture array index
 * level: The mip level, with level 0 containing a full size version of the texture
`
  )
  .params(u =>
    u
      .combine('stage', kShortShaderStages)
      .combine('format', kAllTextureFormats)
      .filter(t => isFillable(t.format))
      .combine('texture_type', ['texture_2d_array', 'texture_depth_2d_array'] as const)
      .filter(
        t => !(t.texture_type === 'texture_depth_2d_array' && !isDepthTextureFormat(t.format))
      )
      .beginSubcases()
      .combine('samplePoints', kSamplePointMethods)
      .combineWithParams([
        { C: 'i32', A: 'u32', L: 'u32' },
        { C: 'u32', A: 'u32', L: 'u32' },
        { C: 'u32', A: 'i32', L: 'u32' },
        { C: 'u32', A: 'u32', L: 'i32' },
      ] as const)
      .combine('depthOrArrayLayers', [1, 8] as const)
  )
  .fn(async t => {
    const { texture_type, format, stage, samplePoints, C, A, L, depthOrArrayLayers } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureLoadNotSupportedForTextureType(texture_type);

    // We want at least 4 blocks or something wide enough for 3 mip levels.
    const [width, height] = chooseTextureSize({ minSize: 8, minBlocks: 4, format });
    const size = { width, height, depthOrArrayLayers };
    const descriptor: GPUTextureDescriptor = {
      format,
      size,
      usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
      mipLevelCount: maxMipLevelCount({ size }),
      ...(t.isCompatibility && { textureBindingViewDimension: '2d-array' }),
    };
    const { texels, texture } = await createTextureWithRandomDataAndGetTexels(t, descriptor);

    const calls: TextureCall<vec2>[] = generateTextureBuiltinInputs2D(50, {
      method: samplePoints,
      descriptor,
      mipLevel: { num: texture.mipLevelCount, type: L },
      arrayIndex: { num: texture.depthOrArrayLayers, type: A },
      hashInputs: [stage, format, samplePoints, C, L, A],
    }).map(({ coords, mipLevel, arrayIndex }) => {
      return {
        builtin: 'textureLoad',
        coordType: C === 'i32' ? 'i' : 'u',
        levelType: L === 'i32' ? 'i' : 'u',
        arrayIndexType: A === 'i32' ? 'i' : 'u',
        arrayIndex,
        mipLevel,
        coords: normalizedCoordToTexelLoadTestCoord(descriptor, mipLevel, C, coords),
      };
    });
    const textureType = appendComponentTypeForFormatToTextureType(texture_type, texture.format);
    const viewDescriptor: GPUTextureViewDescriptor = { dimension: '2d-array' };
    const sampler = undefined;
    const results = await doTextureCalls(
      t,
      texture,
      viewDescriptor,
      textureType,
      sampler,
      calls,
      stage
    );
    const res = await checkCallResults(
      t,
      { texels, descriptor, viewDescriptor },
      textureType,
      sampler,
      calls,
      results,
      stage,
      texture
    );
    t.expectOK(res);
  });

g.test('storage_textures_1d')
  .specURL('https://www.w3.org/TR/WGSL/#textureload')
  .desc(
    `
C is i32 or u32

fn textureLoad(t: texture_storage_1d<format, read>, coords: C) -> vec4<f32>

Parameters:
 * t: The sampled texture to read from
 * coords: The 0-based texel coordinate
`
  )
  .params(u =>
    u
      .combine('stage', kShortShaderStages)
      .combine('format', kPossibleStorageTextureFormats)
      .beginSubcases()
      .combine('samplePoints', kSamplePointMethods)
      .combine('C', ['i32', 'u32'] as const)
  )
  .beforeAllSubcases(t =>
    t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures')
  )
  .fn(async t => {
    const { format, stage, samplePoints, C } = t.params;

    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureFormatNotUsableAsStorageTexture(format);
    skipIfStorageTexturesNotSupportedInStage(t, stage);

    // We want at least 3 blocks or something wide enough for 3 mip levels.
    const [width] = chooseTextureSize({ minSize: 8, minBlocks: 4, format });
    const size = [width, 1];
    const descriptor: GPUTextureDescriptor = {
      format,
      size,
      usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING,
      dimension: '1d',
    };
    const { texels, texture } = await createTextureWithRandomDataAndGetTexels(t, descriptor);

    const calls: TextureCall<vec1>[] = generateTextureBuiltinInputs1D(50, {
      method: samplePoints,
      descriptor,
      hashInputs: [stage, format, samplePoints, C],
    }).map(({ coords }) => {
      return {
        builtin: 'textureLoad',
        coordType: C === 'i32' ? 'i' : 'u',
        coords: normalizedCoordToTexelLoadTestCoord(descriptor, 0, C, coords),
      };
    });
    const textureType = `texture_storage_1d<${format}, read>`;
    const viewDescriptor = {};
    const sampler = undefined;
    const results = await doTextureCalls(
      t,
      texture,
      viewDescriptor,
      textureType,
      sampler,
      calls,
      stage
    );
    const res = await checkCallResults(
      t,
      { texels, descriptor, viewDescriptor },
      textureType,
      sampler,
      calls,
      results,
      stage,
      texture
    );
    t.expectOK(res);
  });

g.test('storage_textures_2d')
  .specURL('https://www.w3.org/TR/WGSL/#textureload')
  .desc(
    `
C is i32 or u32

fn textureLoad(t: texture_storage_2d<format, read>, coords: vec2<C>) -> vec4<f32>

Parameters:
 * t: The sampled texture to read from
 * coords: The 0-based texel coordinate
`
  )
  .params(u =>
    u
      .combine('stage', kShortShaderStages)
      .combine('format', kPossibleStorageTextureFormats)
      .beginSubcases()
      .combine('samplePoints', kSamplePointMethods)
      .combine('baseMipLevel', [0, 1] as const)
      .combine('C', ['i32', 'u32'] as const)
  )
  .beforeAllSubcases(t =>
    t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures')
  )
  .fn(async t => {
    const { format, stage, samplePoints, C, baseMipLevel } = t.params;

    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureFormatNotUsableAsStorageTexture(format);
    skipIfStorageTexturesNotSupportedInStage(t, stage);

    // We want at least 3 blocks or something wide enough for 3 mip levels.
    const size = chooseTextureSize({ minSize: 8, minBlocks: 3, format });
    const descriptor: GPUTextureDescriptor = {
      format,
      size,
      usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING,
      mipLevelCount: 3,
    };
    const viewDescriptor = {
      baseMipLevel,
      mipLevelCount: 1,
    };
    const { texels, texture } = await createTextureWithRandomDataAndGetTexels(t, descriptor);
    const softwareTexture = { texels, descriptor, viewDescriptor };
    const calls: TextureCall<vec2>[] = generateTextureBuiltinInputs2D(50, {
      method: samplePoints,
      softwareTexture,
      hashInputs: [stage, format, samplePoints, C],
    }).map(({ coords }) => {
      return {
        builtin: 'textureLoad',
        coordType: C === 'i32' ? 'i' : 'u',
        coords: normalizedCoordToTexelLoadTestCoord(descriptor, 0, C, coords),
      };
    });
    const textureType = `texture_storage_2d<${format}, read>`;
    const sampler = undefined;
    const results = await doTextureCalls(
      t,
      texture,
      viewDescriptor,
      textureType,
      sampler,
      calls,
      stage
    );
    const res = await checkCallResults(
      t,
      softwareTexture,
      textureType,
      sampler,
      calls,
      results,
      stage,
      texture
    );
    t.expectOK(res);
  });

g.test('storage_textures_2d_array')
  .specURL('https://www.w3.org/TR/WGSL/#textureload')
  .desc(
    `
C is i32 or u32
A is i32 or u32

fn textureLoad(t: texture_storage_2d<format, read>, coords: vec2<C>, array_index: A) -> vec4<f32>

Parameters:
 * t: The sampled texture to read from
 * coords: The 0-based texel coordinate
 * array_index: The 0-based texture array index
`
  )
  .params(u =>
    u
      .combine('stage', kShortShaderStages)
      .combine('format', kPossibleStorageTextureFormats)
      .beginSubcases()
      .combine('samplePoints', kSamplePointMethods)
      .combine('C', ['i32', 'u32'] as const)
      .combine('A', ['i32', 'u32'] as const)
      .combine('depthOrArrayLayers', [1, 8] as const)
      .combine('baseMipLevel', [0, 1] as const)
      .combine('baseArrayLayer', [0, 1] as const)
      .unless(t => t.depthOrArrayLayers === 1 && t.baseArrayLayer !== 0)
  )
  .beforeAllSubcases(t => {
    t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
    t.skipIf(
      t.isCompatibility && t.params.baseArrayLayer !== 0,
      'compatibility mode does not support array layer sub ranges'
    );
  })
  .fn(async t => {
    const { format, stage, samplePoints, C, A, depthOrArrayLayers, baseMipLevel, baseArrayLayer } =
      t.params;

    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureFormatNotUsableAsStorageTexture(format);
    skipIfStorageTexturesNotSupportedInStage(t, stage);

    // We want at least 3 blocks or something wide enough for 3 mip levels.
    const [width, height] = chooseTextureSize({ minSize: 8, minBlocks: 4, format });
    const size = { width, height, depthOrArrayLayers };
    const descriptor: GPUTextureDescriptor = {
      format,
      size,
      usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING,
      ...(t.isCompatibility && { textureBindingViewDimension: '2d-array' }),
      mipLevelCount: 3,
    };
    const viewDescriptor: GPUTextureViewDescriptor = {
      dimension: '2d-array',
      baseMipLevel,
      mipLevelCount: 1,
      baseArrayLayer,
    };
    const { texels, texture } = await createTextureWithRandomDataAndGetTexels(t, descriptor);
    const softwareTexture = { texels, descriptor, viewDescriptor };

    const calls: TextureCall<vec2>[] = generateTextureBuiltinInputs2D(50, {
      method: samplePoints,
      softwareTexture,
      arrayIndex: { num: texture.depthOrArrayLayers - baseArrayLayer, type: A },
      hashInputs: [stage, format, samplePoints, C, A],
    }).map(({ coords, arrayIndex }) => {
      return {
        builtin: 'textureLoad',
        coordType: C === 'i32' ? 'i' : 'u',
        coords: normalizedCoordToTexelLoadTestCoord(descriptor, 0, C, coords),
        arrayIndexType: A === 'i32' ? 'i' : 'u',
        arrayIndex,
      };
    });
    const textureType = `texture_storage_2d_array<${format}, read>`;
    const sampler = undefined;
    const results = await doTextureCalls(
      t,
      texture,
      viewDescriptor,
      textureType,
      sampler,
      calls,
      stage
    );
    const res = await checkCallResults(
      t,
      { texels, descriptor, viewDescriptor },
      textureType,
      sampler,
      calls,
      results,
      stage,
      texture
    );
    t.expectOK(res);
  });

g.test('storage_textures_3d')
  .specURL('https://www.w3.org/TR/WGSL/#textureload')
  .desc(
    `
C is i32 or u32

fn textureLoad(t: texture_storage_2d<format, read>, coords: vec3<C>) -> vec4<f32>

Parameters:
 * t: The sampled texture to read from
 * coords: The 0-based texel coordinate
`
  )
  .params(u =>
    u
      .combine('stage', kShortShaderStages)
      .combine('format', kPossibleStorageTextureFormats)
      .beginSubcases()
      .combine('samplePoints', kSamplePointMethods)
      .combine('C', ['i32', 'u32'] as const)
  )
  .beforeAllSubcases(t => {
    t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
  })
  .fn(async t => {
    const { format, stage, samplePoints, C } = t.params;

    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureFormatNotUsableAsStorageTexture(format);
    skipIfStorageTexturesNotSupportedInStage(t, stage);

    // We want at least 3 blocks or something wide enough for 3 mip levels.
    const size = chooseTextureSize({ minSize: 8, minBlocks: 4, format, viewDimension: '3d' });
    const descriptor: GPUTextureDescriptor = {
      format,
      size,
      usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING,
      dimension: '3d',
    };
    const { texels, texture } = await createTextureWithRandomDataAndGetTexels(t, descriptor);

    const calls: TextureCall<vec3>[] = generateTextureBuiltinInputs3D(50, {
      method: samplePoints,
      descriptor,
      hashInputs: [stage, format, samplePoints, C],
    }).map(({ coords }) => {
      return {
        builtin: 'textureLoad',
        coordType: C === 'i32' ? 'i' : 'u',
        coords: normalizedCoordToTexelLoadTestCoord(descriptor, 0, C, coords),
      };
    });
    const textureType = `texture_storage_3d<${format}, read>`;
    const viewDescriptor = {};
    const sampler = undefined;
    const results = await doTextureCalls(
      t,
      texture,
      viewDescriptor,
      textureType,
      sampler,
      calls,
      stage
    );
    const res = await checkCallResults(
      t,
      { texels, descriptor, viewDescriptor },
      textureType,
      sampler,
      calls,
      results,
      stage,
      texture
    );
    t.expectOK(res);
  });
